import type { Attachment, Parameter, Stage } from "@server/src/types/allure";
import { ChevronRight, CircleCheckIcon, CircleXIcon, FileTextIcon } from "lucide-react";
import { memo, useCallback, useEffect, useMemo, useState } from "react";
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
import { cn } from "@/lib/utils";

export type TreeNodesProps = {
  data: Stage[];
  className?: string;
  onNodeClick?: (node: Stage) => void;
  onNodeExpand?: (nodeId: string, expanded: boolean) => void;
  defaultExpandedIds?: string[];
  showLines?: boolean;
  selectable?: boolean;
  multiSelect?: boolean;
  selectedIds?: string[];
  onSelectionChange?: (selectedIds: string[]) => void;
  indent?: number;
};

const generateNodeId = (node: Stage) => {
  return `${node.name}-${node.time?.start}`;
};

const AttachmentsRenderer = memo(({ attachments }: { attachments: Attachment[] }) => (
  <Accordion type="single" collapsible>
    {attachments.map((attachment) => (
      <AccordionItem value={attachment.name} key={attachment.uid}>
        <AccordionTrigger className="py-1 text-xs">
          <div className="flex items-center gap-1">
            <FileTextIcon className="h-3.5 w-3.5" />
            <p>{attachment.name}</p>
          </div>
        </AccordionTrigger>
        <AccordionContent className="truncate p-0 text-xs">
          <pre>{JSON.stringify(attachment, null, 2)}</pre>
        </AccordionContent>
      </AccordionItem>
    ))}
  </Accordion>
));

const ParametersRenderer = memo(({ parameters }: { parameters: Parameter[] }) => (
  <Table className="border">
    <TableBody>
      {parameters.map((parameter) => (
        <TableRow
          key={parameter.name}
          className="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r [&>:not(:last-child)]:bg-muted/50 [&_td]:px-2 [&_td]:py-1 [&_td]:text-xs"
        >
          <TableCell>{parameter.name}</TableCell>
          <TableCell>{parameter.value}</TableCell>
        </TableRow>
      ))}
    </TableBody>
  </Table>
));

const TreeLines = memo(
  ({
    level,
    currentPath,
    indent,
    isLast,
  }: {
    level: number;
    currentPath: boolean[];
    indent: number;
    isLast: boolean;
  }) => (
    <div className="pointer-events-none absolute top-0 bottom-0 left-0">
      <div className="pointer-events-none absolute top-0 bottom-0 left-0">
        {currentPath.map((isLastInPath, pathIndex) => (
          <div
            // biome-ignore lint/suspicious/noArrayIndexKey: <pathIndex>
            key={`line-${level}-${pathIndex}`}
            className="absolute top-0 bottom-0 border-border/40 border-l"
            style={{
              left: pathIndex * indent + 12,
              display: pathIndex === currentPath.length - 1 && isLastInPath ? "none" : "block",
            }}
          />
        ))}
      </div>
      <div
        className="absolute top-1/2 border-border/40 border-t"
        style={{
          left: (level - 1) * indent + 12,
          width: indent - 4,
          transform: "translateY(-1px)",
        }}
      />
      {isLast && (
        <div
          className="absolute top-0 border-border/40 border-l"
          style={{
            left: (level - 1) * indent + 12,
            height: "50%",
          }}
        />
      )}
    </div>
  ),
);

const TreeNodeItem = memo(
  ({
    node,
    level = 0,
    isLast = false,
    parentPath = [],
    expandedIds,
    selectedIds,
    showLines,
    selectable,
    indent,
    onToggleExpanded,
    onSelect,
    onNodeClick,
  }: {
    node: Stage;
    level?: number;
    isLast?: boolean;
    parentPath?: boolean[];
    expandedIds: Set<string>;
    selectedIds: string[];
    showLines: boolean;
    selectable: boolean;
    indent: number;
    onToggleExpanded: (nodeId: string) => void;
    onSelect: (nodeId: string, ctrlKey: boolean) => void;
    onNodeClick?: (node: Stage) => void;
  }) => {
    const hasChildren = node.steps?.length > 0;
    const nodeId = useMemo(() => generateNodeId(node), [node]);
    const isExpanded = expandedIds.has(nodeId);
    const isSelected = selectedIds.includes(nodeId);
    const currentPath = useMemo(() => [...parentPath, isLast], [parentPath, isLast]);

    return (
      <>
        <div
          className={cn(
            "group relative flex cursor-pointer items-center rounded-none p-1 transition-all duration-200",
            "hover:bg-accent/50",
            isSelected && "bg-accent/80",
            selectable && "hover:border-accent-foreground/10",
          )}
          style={{ paddingLeft: level * indent + 8 }}
          onClick={(e) => {
            if (hasChildren) onToggleExpanded(nodeId);
            onSelect(nodeId, e.ctrlKey || e.metaKey);
            onNodeClick?.(node);
          }}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              if (hasChildren) onToggleExpanded(nodeId);
              onSelect(nodeId, e.ctrlKey || e.metaKey);
              onNodeClick?.(node);
            }
          }}
          tabIndex={0}
          role="treeitem"
          aria-expanded={hasChildren ? isExpanded : undefined}
          aria-selected={selectable ? isSelected : undefined}
        >
          {/* Tree Lines */}
          {showLines && level > 0 && (
            <TreeLines level={level} currentPath={currentPath} indent={indent} isLast={isLast} />
          )}
          {hasChildren && (
            <button
              type="button"
              className="mr-1 flex h-4 w-4 items-center justify-center"
              onClick={(e) => {
                e.stopPropagation();
                onToggleExpanded(nodeId);
              }}
              onKeyDown={(e) => {
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  onToggleExpanded(nodeId);
                }
              }}
              aria-label={isExpanded ? "Collapse" : "Expand"}
              tabIndex={0}
            >
              <ChevronRight
                className={cn(
                  "size-4 transition-all duration-100 ease-linear",
                  isExpanded && "rotate-90",
                  node.status === "failed" ? "text-destructive" : "",
                )}
              />
            </button>
          )}
          <div
            className={cn("flex w-full flex-col gap-2", !hasChildren && node.hasContent && "ml-1")}
          >
            <div className="flex items-center justify-start gap-1">
              {!hasChildren &&
                (node.status === "passed" ? (
                  <CircleCheckIcon className="size-4" />
                ) : (
                  <CircleXIcon className="size-4 text-destructive" />
                ))}
              <span
                className={cn(
                  "truncate text-sm",
                  node.status === "passed" ? "" : "text-destructive",
                )}
              >
                {node.name}
              </span>
            </div>
            {node.parameters.length > 0 && <ParametersRenderer parameters={node.parameters} />}
            {node.attachments.length > 0 && <AttachmentsRenderer attachments={node.attachments} />}
          </div>
        </div>
        {hasChildren && isExpanded && (
          <div className="overflow-hidden">
            {node.steps.map((child, index) => (
              <TreeNodeItem
                key={generateNodeId(child)}
                node={child}
                level={level + 1}
                isLast={index === node.steps.length - 1}
                parentPath={currentPath}
                expandedIds={expandedIds}
                selectedIds={selectedIds}
                showLines={showLines}
                selectable={selectable}
                indent={indent}
                onToggleExpanded={onToggleExpanded}
                onSelect={onSelect}
                onNodeClick={onNodeClick}
              />
            ))}
          </div>
        )}
      </>
    );
  },
);

export function TreeNodesTree({
  data,
  className,
  onNodeClick,
  onNodeExpand,
  defaultExpandedIds = [],
  showLines = true,
  selectable = true,
  multiSelect = false,
  selectedIds = [],
  onSelectionChange,
  indent = 16,
}: TreeNodesProps) {
  const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set(defaultExpandedIds));
  const [internalSelectedIds, setInternalSelectedIds] = useState<string[]>(selectedIds);
  const isControlled = onSelectionChange !== undefined;
  const currentSelectedIds = isControlled ? selectedIds : internalSelectedIds;

  useEffect(() => {
    if (defaultExpandedIds.length > 0) {
      setExpandedIds(new Set(defaultExpandedIds));
    }
  }, [defaultExpandedIds]);

  useEffect(() => {
    if (isControlled) {
      setInternalSelectedIds(selectedIds);
    }
  }, [selectedIds, isControlled]);

  const toggleExpanded = useCallback(
    (nodeId: string) => {
      setExpandedIds((prev) => {
        const newSet = new Set(prev);
        const isExpanded = newSet.has(nodeId);
        isExpanded ? newSet.delete(nodeId) : newSet.add(nodeId);
        onNodeExpand?.(nodeId, !isExpanded);
        return newSet;
      });
    },
    [onNodeExpand],
  );

  const handleSelection = useCallback(
    (nodeId: string, ctrlKey = false) => {
      if (!selectable) return;
      let newSelection: string[];
      if (multiSelect && ctrlKey) {
        newSelection = currentSelectedIds.includes(nodeId)
          ? currentSelectedIds.filter((id) => id !== nodeId)
          : [...currentSelectedIds, nodeId];
      } else {
        newSelection = currentSelectedIds.includes(nodeId) ? [] : [nodeId];
      }
      if (isControlled) {
        onSelectionChange?.(newSelection);
      } else {
        setInternalSelectedIds(newSelection);
      }
    },
    [selectable, multiSelect, currentSelectedIds, isControlled, onSelectionChange],
  );

  const treeNodes = useMemo(() => {
    return data.map((node, index) => {
      return (
        <TreeNodeItem
          key={generateNodeId(node)}
          node={node}
          level={0}
          isLast={index === data.length - 1}
          expandedIds={expandedIds}
          selectedIds={currentSelectedIds}
          showLines={showLines}
          selectable={selectable}
          indent={indent}
          onToggleExpanded={toggleExpanded}
          onSelect={handleSelection}
          onNodeClick={onNodeClick}
        />
      );
    });
  }, [
    data,
    expandedIds,
    currentSelectedIds,
    showLines,
    selectable,
    indent,
    toggleExpanded,
    handleSelection,
    onNodeClick,
  ]);

  return (
    <div className={cn("w-full", className)} role="tree" aria-multiselectable={multiSelect}>
      {treeNodes}
    </div>
  );
}
